<?php

# ---------------------------- s: building super html obj

function shCreate() {
    $r = array();
    $r['/shobj'] = 1;
    $r['/'] = array();
    $content = &$r['/'];

//    $args = func_get_args();

    foreach (func_get_args () as $a) {
        $content[] = $a;
    }

    return $r;
}

function shPush(&$sh) {
//    $args = func_get_args();
//    $num = func_num_args();
//    l($num === 0);
//    if (func_num_args() === 0);
//        return false;

    if (!isset($sh['/shobj'])) {
        $sh = shCreate();
    }

    $content = &$sh['/'];

    $flag = 0;
    foreach (func_get_args () as $a) {
        if ($flag)
            $content[] = $a;
        else
            $flag = 1;
    }
}

function shSet(&$rootObj, $templateName, &$subObj) {
    $rootObj[$templateName] = &$subObj;
}

function shGet(&$rootObj, $templateName, &$subObj) {
    $subObj = $rootObj[$templateName];
    $rootObj[$templateName] = &$subObj;
}

function shCloneTo(&$rootObj, $templateName, &$subObj) {
    $rootObj[$templateName] = $subObj;
}

function shCloneFrom(&$rootObj, $templateName, &$subObj) {
    $subObj = $rootObj[$templateName];
}

function shClone(&$shObj) {
    return $shObj;
}

function shHtml(&$shObj, $indentation = false, $newline = true) {
    if ($shObj['/shobj'] == 1) {
        // parsing the shl into instruction terms
//        $time0 = microtime(true);
//        n('start generating html');
//        $parsedInstr = array();
        // ----------------- benchmark ------------------
        $r = _parse_shl_code($parsedInstr, $shObj['/'], $shObj, $indentation, $newline);
        if ($r) {
//            t('sh warning: ', $r);
            return '';
        }
//        l('parsed instr', $parsedInstr);
        // populate the instruction terms with data
//        $time1 = microtime(true);
//        n(($time1 - $time0) . '/' . ($time1 - $time0));
//        n('start popluating data');
        // ----------------- benchmark ------------------
//        $finalInstr = array();
//        $instrPtr = 0;
//        $r = _populate_sh_instr($shObj, $parsedInstr, $instrPtr, $finalInstr);
//        if ($r) {
//            t('sh warning: ', $r);
//            return '';
//        }
//        unset($parsedInstr);
//        l('final instr', $finalInstr);
        // compiling tree for the html
//        $time2 = microtime(true);
//        n(($time2 - $time1) . '/' . ($time2 - $time0));
//        n('start compiled tree obj');
        // ----------------- benchmark ------------------
        $tagStack = array();
        $stackIndex = 0;
        $r = _compile_tree_obj($parsedInstr, $tagStack, $stackIndex);
        if ($r) {
//            t('sh warning: ', $r);
            return '';
        }
        unset($finalInstr);

        // generating html text array
//        $time3 = microtime(true);
//        n(($time3 - $time2) . '/' . ($time3 - $time0));
//        n('start generating html text array');
        // ----------------- benchmark ------------------
        $htmlText = array();
        $r = _generate_html($tagStack[0]['content'], $htmlText, 0, $indentation, $newline);
        if ($r) {
//            t('sh warning: ', $r);
            return '';
        }
        unset($tagStack);

        // final return the html text
//        $time4 = microtime(true);
//        n(($time4 - $time3) . '/' . ($time4 - $time0));
//        n('finally generating html text');
        // ----------------- benchmark ------------------
//        $html = implode('', $htmlText);
//        unset($htmlText);
//        $time5 = microtime(true);
//        n(($time5 - $time4) .'/'. ($time5 - $time0));
//        n('end');
        // ----------------- benchmark ------------------
        return implode('', $htmlText);
    } else {
        if (is_string($shObj)) {
            return $shObj;
        } else {
            return '';
        }
    }
}

function shQuick() {
    $args = func_get_args();
    return shHtml(shCreate($args));
}

# ---------------------------- e: building super html obj
# ---------------------------- s: util functions

function _is_word_char($c) {
    $cCode = ord($c);
    return ($cCode >= 65 && $cCode <= 90) || ($cCode >= 97 && $cCode <= 122) ||
    ($cCode >= 48 && $cCode <= 57) || $c == '_';
}

function _is_space_char($c) {
    return $c == ' ' || $c == "\n" || $c == "\t";
}

function _get_word(&$curr, &$ptr) {
    $r = array();

    for ($strlen = strlen($curr); $ptr < $strlen; $ptr++) {
        $c = $curr[$ptr];
        if (_is_word_char($c)) {
            $r[] = $c;
        } else {
            return implode('', $r);
        }
    }

    return implode('', $r);
}

function _escape_for_html($s) {
    return htmlentities($s);
}

function _escape_for_attr($s) {
    return strtr($s, array(
        "'" => "\'"
    ));
}

function _get_value_for_var($vtype, $var, $isAttr = true) {

    if ($var === null) {
        return null;
    }
    if ($vtype == 's') {
        if (is_array($var)) {
            if ($isAttr) {
                $var = implode("", $var);
            } else {
                $var = implode("\n", $var);
            }
        }
        if ($isAttr) {
            return _escape_for_attr($var);
        } else {
            return _escape_for_html($var);
        }
    } else if ($vtype == 'd') {
        if (is_array($var)) {
            return false;
        }
        return (int) $var;
    } else if ($vtype == 'f') {
        if (is_array($var)) {
            return false;
        }
        return (float) $var;
    } else if ($vtype == 'ne') {
        if (is_array($var)) {
            if ($isAttr) {
                $var = implode("", $var);
            } else {
                $var = implode("\n", $var);
            }
        }
        return $var;
    } else {
        return false;
    }
}

function _get_until_not_char(&$s, &$ptr, $cond) {
    $r = array();
    for ($l = strlen($s); $ptr < $l; $ptr++) {
        $c = $s[$ptr];
        if ($c == $cond) {
            $r[] = $c;
        } else {
            break;
        }
    }

    $res['cond'] = false;
    $res['r'] = implode('', $r);
    return $res;
}

function _get_until_char(&$s, &$ptr, $cond) {
    $r = array();
//    fl('here until char');
    for ($l = strlen($s); $ptr < $l; $ptr++) {
        $c = $s[$ptr];
//        fl($c);
        if ($c != $cond) {
            $r[] = $c;
        } else {
            $res['cond'] = true;
            // if the cond is met
            $res['r'] = implode('', $r);
            return $res;
        }
    }

    $res['cond'] = false;
    // the cond is not met
    $res['r'] = implode('', $r);
    return $res;
}

function _get_char_for_attr($c) {
    if ($c == '\'') {
        return '\\\'';
    } else {
        return $c;
    }
}

function _if_match_close_cond(&$cond, &$tag) {
    if (isset($cond['tag']) && $tag['tag'] != $cond['tag']) {
        return false;
    }
    if (isset($cond['id']) && $tag['id'] != $cond['id']) {
        return false;
    }
    foreach ((array) $cond['class'] as $class => $v) {
        if (!isset($tag['class'][$class]))
            return false;
    }
    if (isset($cond['hidden']) && $tag['hidden'] != $cond['hidden'])
        return false;
    return true;
}

/**
 * skip all spaces
 * return the char when non-space char is met
 * return false if the end is met
 *
 * @param <type> $curr
 * @param <type> $ptr
 * @return <type>
 */
function _skip_all_space(&$curr, &$ptr) {
    $l = strlen($curr);
    while ($ptr < $l) {
        $c = $curr[$ptr];
        if (!_is_space_char($c)) {
            return $c;
        }
        $ptr++;
    }
    return false;
}

# ---------------------------- e: util functions
# ---------------------------- first run : parsing shl

function _parse_shl_code(&$compiledInstr, &$currAry, &$shObj, $indentation, $newline) {

    $aryPtr = 0;
    $countAry = count($currAry);
    while ($aryPtr < $countAry) {
        $curr = $currAry[$aryPtr];
        if (is_string($curr)) {
            $ptr = 0;
            $l = strlen($curr);
            while ($ptr < $l) {
                $res = array();
                $c = $curr[$ptr];
//                fl($c, $ptr);
                if (_is_word_char($c)) {
                    // A Z a z 0-9 _
                    // --------------- tag, attr, for|if --------------
                    $res = _parse_char($curr, $ptr);
                    if ($res === false) {
                        return "Error @ '$curr'";
                    }
                    if ($res['type'] == 'attr') {
                        if (!isset($res['f'])) {
                            if ($res['content'] == '') {
                                $res['content'] = _get_value_for_var($res['vtype'], $currAry[++$aryPtr]);
                            } else {
                                $res['content'] = _get_value_for_var($res['vtype'], $shObj[$res['content']], $res['type'] == 'attr' ? true : false);
                            }
                        }
                    }
                    $compiledInstr[] = $res;
                } else if ($c == ':') {
                    // ------------- html ---------------
                    $ptr++;
                    $res = _parse_attr($curr, $ptr);
                    if ($res === false) {
                        return "Error @ '$curr'";
                    }
                    $res['type'] = 'html';
                    if (!isset($res['f'])) {
                        if ($res['content'] == '') {
                            $res['content'] = _get_value_for_var($res['vtype'], $currAry[++$aryPtr], false);
                        } else {
                            $res['content'] = _get_value_for_var($res['vtype'], $shObj[$res['content']], $res['type'] == 'attr' ? true : false);
                        }
                    }
                    $compiledInstr[] = $res;
                } else if ($c == '/') {
                    // ------------------ template var --------------------
                    $ptr++;
                    $c = $curr[$ptr];
                    if (_is_word_char($c)) {
                        $w = _get_word($curr, $ptr);
                        if ($w) {
                            $res['type'] = 'html';
                            if (isset($shObj[$w])) {
                                $res['content'] = shHtml($shObj[$w]);
                            }
                            $compiledInstr[] = $res;
                        } else {
                            return "Error @ '$curr'";
                        }
                    } else if ($c == '/') {
                        $ptr++;
                        $w = _get_word($curr, $ptr);
                        if ($w == 'efor' || $w == 'eif') {
                            $res['type'] = $w;
                            $compiledInstr[] = $res;
                        } else {
                            return "Error @ '$curr'";
                        }
                    } else {
                        return "Error @ '$curr'";
                    }
                } else if ($c == '#') {
                    // ----------------- comment ------------------
                    $ptr++;
                    $comment = _get_until_char($curr, $ptr, '#');
                    $ptr++;
                    if ($comment['cond']) {
                        $res['type'] = 'comment';
                        $res['content'] = $comment['r'];
                        $compiledInstr[] = $res;
                    } else {
                        return "Error @ '$curr'";
                    }
                } else if ($c == '&') {
                    // ----------------- quick html ------------------
                    $ptr++;
                    $quickHtml = _get_until_char($curr, $ptr, '&');
                    $ptr++;
                    if ($quickHtml['cond']) {
                        $res['type'] = 'html';
                        $res['content'] = _escape_for_html($quickHtml['r']);
//                        fl($quickHtml['r'], _escape_for_html($quickHtml['r']));
                        $compiledInstr[] = $res;
                    } else {
                        return "Error @ '$curr'";
                    }
                } else if ($c == '<') {
                    // ----------------- closing tag ----------------------
                    $r = _get_until_not_char($curr, $ptr, '<');
                    $res['type'] = 'close';
                    if (strlen($r['r']) == 1) {
                        // only one '<'

                        if (_is_space_char($curr[$ptr]) || $ptr == $l) {
                            // if there is only one <
                            $res['level'] = 1;
                        } else {
                            $tmpPtr = $ptr;
                            $w = _get_word($curr, $ptr);
                            if (is_numeric($w)) {
                                // if there is a number after <
                                $res['level'] = $w;
                            } else {
                                // if there is a set of closing conditions
                                // reset the ptr, because the word is not numeric, we need to do it again
                                $ptr = $tmpPtr;
                                $r = _parse_conditional_close($curr, $ptr);
                                if ($r) {
                                    $res['cond'] = $r;
                                } else {
                                    return "Error @ '$curr'";
                                }
                            }
                        }
                    } else {
                        // more than one '<'
                        $res['level'] = strlen($r['r']);
                    }
                    $compiledInstr[] = $res;
                } else if (_is_space_char($c)) {
                    $ptr++;
                } else {
                    return "Error @ '$curr'";
                }
            } // end of while $ptr < $l
        } else if (is_array($curr)) {
            if ($curr['/shobj'] == 1) {
                $res['type'] = 'html';
                $res['content'] = shHtml($curr, $indentation, $newline);
                $compiledInstr[] = $res;
            } else {
                _parse_shl_code($compiledInstr, $curr, $shObj, $indentation, $newline);
            }
        }
        $aryPtr++;
    }
}

/**
 * possible :
 *
 * tag, type('tag')
 * id
 * class []
 * hidden
 * cond [tag, class[], id, hidden]
 *
 * attr, content (string | vtype, name)
 *
 * contr, var(vtype, name)
 *
 * char ->
 *  tag ->
 *      closing_tag
 *  attr
 *  var
 *
 *
 * @param <type> $curr
 * @param <type> $ptr
 * @return <type>
 */
function _parse_char(&$curr, &$ptr) {

    $w = _get_word($curr, $ptr);
    $c = $curr[$ptr];

    // tag, attr, for, if
    if ($c == '#' || $c == '.' || $c == '?' || $c == '>' || $c == '/') {
        $res = _parse_tag($curr, $ptr);
        if ($res === false)
            return false;
        $res['tag'] = $w;
        $res['type'] = 'tag';
    } else if ($c == ':') {
//        fl($ptr, 'before');
        $res = _parse_attr($curr, ++$ptr);
//        fl($ptr, 'after');
//        fl($r);
        $res['attr'] = $w;
        $res['type'] = 'attr';
    } else if ($c == '(') {
        if ($w == 'for' || $w == 'if') {
            $res = _parse_var($curr, ++$ptr);
            if ($res == false || ($w == 'for' && $res['vtype'] != 'ne')) {
                // the return shouldn't be false, and there must be no vtype rather than 'ne' for 'for'
                return false;
            }
            $res['type'] = $w;

            // if it is for, then ) will be expected
            if ($w == 'for') {
                $c = _skip_all_space($curr, $ptr);
                if ($c != ')') {
                    // there must be no other extra chars than spaces
                    // and a ')' must be met before the string ends
                    return false;
                }
            } else {
                // if it is 'if', grab everything from the rest part
                if (!_is_space_char($curr[$ptr])) {
                    return false;
                }
                $value = _get_until_char($curr, ++$ptr, ')');
                if ($value['cond']) {
                    $res['value'] = trim($value['r']);
                } else {
                    // ')' is not met after the if( {var} ...
                    return false;
                }
            }
            $ptr++;
        } else {
            return false;
        }
    } else {
        return false;
    }

    return $res;
}

/**
 * start from the ~ or /
 * @param <type> $curr
 * @param <type> $ptr
 * @return <type>
 */
function _parse_var(&$curr, &$ptr) {
    $l = strlen($curr);
    while ($ptr < $l) {
        $c = $curr[$ptr];
//        echo $c;
        // if the type is denoted
        if ($c == '~') {
            $w = _get_word($curr, ++$ptr);
            if ($w == 's' || $w == 'd' || $w == 'ne' || $w == 'f') {
                $var['vtype'] = $w;
                $var['content'] = '';

                if ($curr[$ptr] == '/') {
                    $w = _get_word($curr, ++$ptr);
                    if ($w)
                        $var['content'] = $w;
                    else
                        return false;
                    // syntax error : '/' should always has a name follows
                }
                return $var;
            }
        } else if ($c == '/') {

            // it is ne by default
            $w = _get_word($curr, ++$ptr);
            if ($w) {
                $var['vtype'] = 'ne';
                $var['content'] = $w;
                return $var;
            }
            else
                return false;

            // syntax error: '/' should always has a name follows
        } else if (!_is_space_char($c)) {
            return false;
        }

        $ptr++;
    }

    return false;
}

function _parse_attr(&$curr, &$ptr, $isAttr = true) {
    $content = array();

//    $hasEscaped = false; // test
    $isWaitingForVarDef = true;
    $l = strlen($curr);
    while ($ptr < $l) {
        $c = $curr[$ptr];
        if ($isWaitingForVarDef && $c == '~') {
            $w = _get_word($curr, ++$ptr);
            if ($w == 's' || $w == 'd' || $w == 'ne' || $w == 'f') {
                // syntax is correct, looking for variable
                $var['vtype'] = $w;

                // if the name of the var is defined
                if ($curr[$ptr] == '/') {
                    $w = _get_word($curr, ++$ptr);
                    if ($w) {
                        $var['content'] = $w;
                    } else {
                        return false;
                    }
                } else {
                    $var['content'] = '';
                }

                return $var;
            } else {
                // ~ is regarded as part of the content
                $content[] = '~';
                if ($w) {
                    $content[] = $w;
                }
                // will not wait for the variable anymore
                $isWaitingForVarDef = false;
                // skip the $ptr++ for parsing the current $c at next run
                continue;
            }
        } else if ($isWaitingForVarDef && $c == '/') {
            $w = _get_word($curr, ++$ptr);
            if ($w) {
                $var['vtype'] = 'ne';
                $var['content'] = $w;
                return $var;
            } else {
                $isWaitingForVarDef = false;
                continue;
            }
        } else if ($c == ',') {
            // if ,, encoutnered, the content ends
            if ($curr[$ptr + 1] == ',') {
                $ptr += 2;
//                fl($content);
//                fl($curr[$ptr], $ptr);

                return array('content' => implode('', $content), 'f' => 1);
            } else {
                $content[] = $c;
            }
        } else if ($c == '/') {
            $ptr++;
            if ($curr[$ptr] == ',') { // if it is /, the ',' will be escaped
                $content[] = ',';
            } else { // adding the char to the array
                $content[] = '/';
                $content[] = $curr[$ptr];
            }
        } else if ($isWaitingForVarDef) {
            if (!_is_space_char($c)) {
                $isWaitingForVarDef = false;
                $content[] = $c;
            }
        } else {
            $content[] = $c;
        }
        $ptr++;
    }

    $var['vtype'] = 'ne';
    return $var;
}

function _parse_tag(&$curr, &$ptr) {
    $res = array();


    for ($l = strlen($curr); $ptr < $l;) {
        $c = $curr[$ptr];
//        echo $c;
        if ($c == '#') { // id
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['id'] = $w;
        } else if ($c == '.') { // class
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['class'][$w] = 1;
        } else if ($c == '?') { //hidden tag
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['hidden'] = $w;
        } else if ($c == '>') { // open tag
            $condClose = _parse_conditional_close($curr, ++$ptr);
            if ($condClose === false) {
                return false;
            } else if (count($condClose) > 0) {
                $res['cond'] = &$condClose;
            }
            $res['level'] = 'n';
            return $res;
        } else if ($c == '/') { // one level tag
            $res['level'] = 1;
            $ptr++;
            return $res;
        } else {
            // syntax error, return false
            return false;
        }
    }

    return false;
}

function _parse_conditional_close(&$curr, &$ptr) {
    $res = null;
    $conds = array();

    $w = _get_word($curr, $ptr);
    if ($w)
        $res['tag'] = $w;

    for ($l = strlen($curr); $ptr < $l;) {

        $c = $curr[$ptr];
        if ($c == '#') { // id
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['id'] = $w;
        } else if ($c == '.') { // class
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['class'][$w] = 1;
        } else if ($c == '?') { //hidden tag
            $w = _get_word($curr, ++$ptr);
            if (!$w)
                return false;
            $res['hidden'] = $w;
        } else if ($c == '|') { // another closing term
            if ($res) {
                // pushing previous closing cond
                $conds[] = $res;
                // start new cond
                $res = null;
                $w = _get_word($curr, ++$ptr);
                if ($w)
                    $res['tag'] = $w;
            } else {
                // no valid closing term is met before '|'
                return false;
            }
        } else if (_is_space_char($c)) { // space encountered, return the result
            if ($res) {
                $conds[] = $res;
            }
            return $conds;
        } else { // invalid char is met
            return false;
        }
    }

    // end of the line, return the result
    if ($res) {
        $conds[] = $res;
    }
    return $conds;
}

# ---------------------------- second run: compiled super html obj

function _populate_sh_instr(&$data, &$compiledInstr, &$instrPtr, &$finalInstr, $target = null, $ifCondMet = true, $count = -1) {
//    if ($count == -1)
//        $count = count($compiledInstr);
//    while ($instrPtr < $count) {
//        $instr = $compiledInstr[$instrPtr++];
//
//        if ($instr['type'] == $target)
//            return;
//
//        if ($instr['type'] == 'for') {
//            $goneThrough = false;
//            // if it is a for loop
//            if (is_array($data[$instr['content']])) {
//                foreach ($data[$instr['content']] as $d) {
//                    $tmpPtr = $instrPtr;
//                    $goneThrough = true;
//                    $r = _populate_sh_instr($d, $compiledInstr, $tmpPtr, $finalInstr, 'efor', $ifCondMet, $count);
//                    if ($r)
//                        return $r;
//                }
//                $instrPtr = $tmpPtr;
//                continue;
//            }
//            if (!$goneThrough) {
//                $d = array();
//                $r = _populate_sh_instr($d, $compiledInstr, $instrPtr, $finalInstr, 'efor', false, $count);
//                if ($r)
//                    return $r;
//                $instrPtr = $tmpPtr;
//                continue;
//            }
//        }
//
//        // first populate var into the variable
//        if (isset($instr['vtype'])) {
//            $instr['content'] = _get_value_for_var($instr['vtype'], $data[$instr['content']], $instr['type'] == 'attr' ? true : false);
//        }
//        if ($instr['type'] == 'if') {
//            if ($instr['content'] == $instr['value']) {
//                $r = _populate_sh_instr($data, $compiledInstr, $instrPtr, $finalInstr, 'eif', $ifCondMet, $count);
//                if ($r)
//                    return $r;
//            } else {
//                $r = _populate_sh_instr($data, $compiledInstr, $instrPtr, $finalInstr, 'eif', false, $count);
//                if ($r)
//                    return $r;
//            }
//            continue;
//        }
//
//        if ($ifCondMet)
//            $finalInstr[] = $instr;
//    }
//
//    if ($target != null) {
//        return "Error @ no $target met";
//    }
}

# ---------------------------- third run: compiling tree object

function _compile_tree_obj(&$finalInstr, &$tagStack, &$stackIndex) {
    $lastTagLevel = 'n';
    foreach ($finalInstr as $instr) {
        switch ($instr['type']) {
            case 'tag':
                _push_tag($stackIndex, $tagStack, $instr, $lastTagLevel);
                break;
            case 'html':
                _push_html($stackIndex, $tagStack, $instr);
                break;
            case 'attr':
                _push_attr($stackIndex, $tagStack, $instr);
                break;
            case 'close':
                _close_tag($stackIndex, $tagStack, $instr, $lastTagLevel);
//                fl($lastTagLevel, $instr);
                break;
            case 'comment':
                _push_comment($stackIndex, $tagStack, $instr);
                break;
//            case 'tpl':
//                _push_tpl($stackIndex, $tagStack, $instr);
//                break;
        }
    }
}

//function _push_tpl(&$stackIndex, &$tagStack, &$instr) {
//    if ($instr['content']) {
//        $tagStack[$stackIndex]['content'][] = shHtml($$instr['content']);
//    }
//}

function _push_html(&$stackIndex, &$tagStack, &$instr) {
    if ($instr['content']) {
        $tagStack[$stackIndex]['content'][] = $instr['content'];
    }
}

function _push_attr(&$stackIndex, &$tagStack, &$instr) {
    $currObj = &$tagStack[$stackIndex];
    if ($instr['content'] !== null) {
        if ($instr['attr'] == 'id') {
            $currObj['id'] = $instr['content'];
        } else if ($instr['attr'] == 'class') {
            $currObj['class'] = array();
            foreach (explode(' ', $instr['content']) as $cls) {
                if ($cls)
                    $currObj['class'][$cls] = 1;
            }
        } else {
            $currObj['attr'][$instr['attr']] = $instr['content'];
        }
    }
}

function _push_comment(&$stackIndex, &$tagStack, &$instr) {
    if ($instr['content']) {
        $tagStack[$stackIndex]['content'][] = "<!--$instr[content]-->";
    }
}

function _close_tag(&$stackIndex, &$tagStack, &$instr, &$lastTagLevel) {
    if ($instr['level'] > 0) {
        // closing tag according to the levels
        $stackIndex -= $instr['level'];
        if ($stackIndex < 0)
            $stackIndex = 0;
        $lastTagLevel = 'n';
    } else {
        // closing tag using conditions
        for ($i = $stackIndex; $i >= 0; $i--) {
            foreach ($instr['cond'] as $cond) {
//                fl($cond);
//                fl($tagStack[$i]);
                if (_if_match_close_cond($cond, $tagStack[$i])) {
//                    fl($i);
                    $stackIndex = $i - 1 < 0 ? 0 : $i - 1;
                    $lastTagLevel = 'n';
//                    fl($stackIndex);
                    return;
                }
            }
        }
    }
}

/**
 * @param $currCompiledObj => 'obj' => &currObj
 * @param $compiledObjStack => array () => & obj
 */
function _push_tag(&$compiledObjStackIndex, &$compiledObjStack, &$tag, &$lastTagLevel) {

    _pop_tag($compiledObjStackIndex, $compiledObjStack, $tag, $lastTagLevel);

    $newTag = $tag;
    $compiledObjStack[$compiledObjStackIndex]['content'][] = &$newTag;
    $compiledObjStackIndex++;
    $compiledObjStack[$compiledObjStackIndex] = &$newTag;
    $lastTagLevel = $tag['level'];
}

/**
 *
 * cond => array (tag, id, class[], hidden)
 * @param <type> $currCompiledObj
 * @param <type> $compiledObjStack
 * @param <type> $tag
 */
function _pop_tag(&$compiledObjStackIndex, &$compiledObjStack, &$tag, &$lastTagLevel) {
    for ($i = 0; $i <= $compiledObjStackIndex; $i++) {
        // if cond is set
        if (isset($compiledObjStack[$i]['cond'])) {
            $conds = $compiledObjStack[$i]['cond'];

            // foreach conditional close tag
            foreach ($conds as $cond) {
                if (_if_match_close_cond($cond, $tag)) {
                    $compiledObjStackIndex = $i - 1 < 0 ? 0 : $i - 1;
                    return;
                }
            }
        }
    }

    if ($lastTagLevel == '1') {
        if ($compiledObjStackIndex > 0)
            $compiledObjStackIndex--;
    }
}

# ---------------------------- forth run: generating html text

function _generate_html(&$treeNodes, &$htmlText, $level, $indentation = true, $newline = true) {
    foreach ((array) $treeNodes as $n) {
        if ($n['type'] == 'tag') {
            if ($indentation)
                _print_indentation($level, $htmlText);
            $htmlText[] = '<';
            $htmlText[] = $n['tag'];
            if ($n['id'] !== null) {
                $htmlText[] = ' id=\'';
                $htmlText[] = $n['id'];
                $htmlText[] = '\'';
            }
            if (is_array($n['class'])) {
                $htmlText[] = ' class=\'';
                $htmlText[] = implode(' ', array_keys($n['class']));
                $htmlText[] = '\'';
            }
            if (is_array($n['attr'])) {
                foreach ($n['attr'] as $attr => $value) {
                    $htmlText[] = ' ';
                    $htmlText[] = $attr;
                    $htmlText[] = '=\'';
                    $htmlText[] = $value;
                    $htmlText[] = '\'';
                }

                if($n['tag'] == 'input' && $n['id'] && !$n['attr']['name']){
                    $htmlText[] = ' name=\'';
                    $htmlText[] = $n['id'];
                    $htmlText[] = '\'';
                }
            } else if($n['tag'] == 'input' && $n['id']){
                $htmlText[] = ' name=\'';
                $htmlText[] = $n['id'];
                $htmlText[] = '\'';
            }
            $htmlText[] = $newline ? ">\n" : '>';

            if ($n['tag'] == 'link' || $n['tag'] == 'meta') {
                continue;
            }

            if (is_array($n['content'])) {
//                fl($n['content']);
                _generate_html($n['content'], $htmlText, $level + 1, $indentation, $newline);
            }
            if ($indentation)
                _print_indentation($level, $htmlText);
            $htmlText[] = '</';
            $htmlText[] = $n['tag'];
            $htmlText[] = $newline ? ">\n" : '>';
        } else {
            if ($indentation)
                _print_indentation($level, $htmlText);
            $htmlText[] = $n;
            if ($newline)
                $htmlText[] = "\n";
        }
    }
}

function _print_indentation($level, &$htmlText) {
    for ($i = 0; $i < $level; $i++) {
        $htmlText[] = '   ';
    }
}